/*******************************************************************************
 *  Copyright (c) 2010 Weltevree Beheer BV, Remain Software & Industrial-TSI
 * 
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 * 
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *   Wim S. Jongman - initial API and implementation
 ******************************************************************************/
package org.eclipse.nebula.widgets.oscilloscope.multichannel;

import java.io.File;

import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;

/**
 * This class keeps the oscilloscope animation running and is used to set
 * various attributes of the scope.
 * 
 * <p/>
 * You need to provide an {@link Oscilloscope} widget by overriding the
 * {@link #getOscilloscope()} method. The {@link Oscilloscope#redraw()} method
 * will be called every {@link #getDelayLoop()} milliseconds. The higher the
 * value, the slower the animation will run.
 * </p>
 * Just before the redraw and just after the redraw the <code>hook..Draw</code>
 * methods are called. Don't do any expensive calculations there because this
 * will slow down the animation.
 * <p/>
 * Then a counter is incremented and if the counter reaches the
 * {@link #getPulse()} value, then the {@link #hookSetValues(int)} method is
 * called. This is you opportunity to provide a value to the scope by calling
 * its setValue or setValues method. The hookSetValues method is only called if
 * the {@link #getPulse()} value is greater than {@link #NO_PULSE}
 * <p/>
 * You can also be called back by the widget if it runs out of values by setting
 * a listener in the
 * {@link Oscilloscope#addStackListener(OscilloscopeStackAdapter)} method.
 * <p/>
 * If you want to speed up the scope, try overriding the
 * {@link #getProgression()} method. This will draw the scope this number of
 * times before actually painting.
 * 
 * @author Wim Jongman
 * 
 */
public class OscilloscopeDispatcher {

	/**
	 * Plays a sound clip.
	 * 
	 */
	public class SoundClip {
		Clip clip = null;
		String oldFile = "";

		/**
		 * Returns the clip so you can control it.
		 * 
		 * @return the Clip
		 */
		public Clip getClip() {
			return this.clip;
		}

		/**
		 * Creates a clip from the passed sound file and plays it. If the clip
		 * is currently playing then the method returns, get the clip with
		 * {@link #getClip()} to control it.
		 * 
		 * @param file
		 * @param loopCount
		 */
		public void playClip(File file, int loopCount) {

			if (file == null)
				return;

			try {

				if ((this.clip == null) || !file.getAbsolutePath().equals(this.oldFile)) {
					this.oldFile = file.getAbsolutePath();
					this.clip = AudioSystem.getClip();
					this.clip.open(AudioSystem.getAudioInputStream(file));
				}
				if (this.clip.isActive())
					return;
				// clip.stop(); << Alternative

				this.clip.setFramePosition(0);
				this.clip.loop(loopCount);

			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	private final SoundClip clipper = new SoundClip();

	/**
	 * Contains a small image that can serve as the background of the scope.
	 */
	final public static int[] BACKGROUND_MONITOR = new int[] { 255, 216, 255, 224, 0, 16, 74, 70, 73, 70, 0, 1, 1, 1, 0, 72, 0, 72, 0, 0,
			255, 254, 0, 19, 67, 114, 101, 97, 116, 101, 100, 32, 119, 105, 116, 104, 32, 71, 73, 77, 80, 255, 219, 0, 67, 0, 5, 3, 4, 4,
			4, 3, 5, 4, 4, 4, 5, 5, 5, 6, 7, 12, 8, 7, 7, 7, 7, 15, 11, 11, 9, 12, 17, 15, 18, 18, 17, 15, 17, 17, 19, 22, 28, 23, 19, 20,
			26, 21, 17, 17, 24, 33, 24, 26, 29, 29, 31, 31, 31, 19, 23, 34, 36, 34, 30, 36, 28, 30, 31, 30, 255, 219, 0, 67, 1, 5, 5, 5, 7,
			6, 7, 14, 8, 8, 14, 30, 20, 17, 20, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
			30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 255, 192, 0, 17, 8,
			0, 20, 0, 20, 3, 1, 34, 0, 2, 17, 1, 3, 17, 1, 255, 196, 0, 23, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, 7,
			255, 196, 0, 34, 16, 0, 2, 2, 0, 5, 5, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 4, 2, 20, 33, 52, 84, 17, 115, 145, 178, 209, 97,
			255, 196, 0, 23, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 3, 255, 196, 0, 27, 17, 0, 2, 2, 3, 1, 0, 0, 0, 0,
			0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 17, 33, 49, 81, 18, 255, 218, 0, 12, 3, 1, 0, 2, 17, 3, 17, 0, 63, 0, 225, 37, 87, 118, 245, 59,
			79, 217, 140, 212, 60, 24, 60, 226, 250, 83, 110, 196, 74, 10, 205, 211, 133, 245, 141, 180, 155, 122, 106, 255, 0, 78, 77,
			187, 88, 4, 164, 237, 96, 204, 5, 89, 168, 120, 48, 121, 197, 244, 10, 223, 5, 233, 240, 148, 170, 238, 222, 167, 105, 251, 48,
			9, 237, 20, 182, 137, 64, 6, 140, 255, 217 };

	/**
	 * Contains a small image that can serve as the background of the scope.
	 */
	final public static int[] BACKGROUND_MONITOR_SMALL = new int[] { 255, 216, 255, 224, 0, 16, 74, 70, 73, 70, 0, 1, 1, 1, 0, 72, 0, 72,
			0, 0, 255, 254, 0, 20, 67, 114, 101, 97, 116, 101, 100, 32, 119, 105, 116, 104, 32, 71, 73, 77, 80, 0, 255, 219, 0, 67, 0, 2,
			1, 1, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 5, 3, 3, 3, 3, 3, 6, 4, 4, 3, 5, 7, 6, 7, 7, 7, 6, 7, 7, 8, 9, 11, 9, 8, 8, 10, 8, 7,
			7, 10, 13, 10, 10, 11, 12, 12, 12, 12, 7, 9, 14, 15, 13, 12, 14, 11, 12, 12, 12, 255, 219, 0, 67, 1, 2, 2, 2, 3, 3, 3, 6, 3, 3,
			6, 12, 8, 7, 8, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
			12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 255, 192, 0, 17, 8, 0, 10, 0, 10, 3, 1,
			34, 0, 2, 17, 1, 3, 17, 1, 255, 196, 0, 31, 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
			10, 11, 255, 196, 0, 181, 16, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125, 1, 2, 3, 0, 4, 17, 5, 18, 33, 49, 65, 6, 19,
			81, 97, 7, 34, 113, 20, 50, 129, 145, 161, 8, 35, 66, 177, 193, 21, 82, 209, 240, 36, 51, 98, 114, 130, 9, 10, 22, 23, 24, 25,
			26, 37, 38, 39, 40, 41, 42, 52, 53, 54, 55, 56, 57, 58, 67, 68, 69, 70, 71, 72, 73, 74, 83, 84, 85, 86, 87, 88, 89, 90, 99,
			100, 101, 102, 103, 104, 105, 106, 115, 116, 117, 118, 119, 120, 121, 122, 131, 132, 133, 134, 135, 136, 137, 138, 146, 147,
			148, 149, 150, 151, 152, 153, 154, 162, 163, 164, 165, 166, 167, 168, 169, 170, 178, 179, 180, 181, 182, 183, 184, 185, 186,
			194, 195, 196, 197, 198, 199, 200, 201, 202, 210, 211, 212, 213, 214, 215, 216, 217, 218, 225, 226, 227, 228, 229, 230, 231,
			232, 233, 234, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 255, 196, 0, 31, 1, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
			0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 255, 196, 0, 181, 17, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119, 0, 1, 2, 3,
			17, 4, 5, 33, 49, 6, 18, 65, 81, 7, 97, 113, 19, 34, 50, 129, 8, 20, 66, 145, 161, 177, 193, 9, 35, 51, 82, 240, 21, 98, 114,
			209, 10, 22, 36, 52, 225, 37, 241, 23, 24, 25, 26, 38, 39, 40, 41, 42, 53, 54, 55, 56, 57, 58, 67, 68, 69, 70, 71, 72, 73, 74,
			83, 84, 85, 86, 87, 88, 89, 90, 99, 100, 101, 102, 103, 104, 105, 106, 115, 116, 117, 118, 119, 120, 121, 122, 130, 131, 132,
			133, 134, 135, 136, 137, 138, 146, 147, 148, 149, 150, 151, 152, 153, 154, 162, 163, 164, 165, 166, 167, 168, 169, 170, 178,
			179, 180, 181, 182, 183, 184, 185, 186, 194, 195, 196, 197, 198, 199, 200, 201, 202, 210, 211, 212, 213, 214, 215, 216, 217,
			218, 226, 227, 228, 229, 230, 231, 232, 233, 234, 242, 243, 244, 245, 246, 247, 248, 249, 250, 255, 218, 0, 12, 3, 1, 0, 2, 17,
			3, 17, 0, 63, 0, 248, 10, 210, 13, 31, 254, 17, 29, 65, 22, 238, 252, 192, 110, 160, 44, 198, 221, 119, 3, 178, 92, 12, 110,
			233, 215, 244, 245, 227, 48, 89, 104, 35, 143, 183, 106, 60, 127, 211, 170, 127, 241, 84, 182, 31, 242, 35, 106, 63, 245, 247,
			111, 255, 0, 160, 203, 88, 213, 143, 41, 162, 143, 153, 255, 217 };

	/**
	 * This is a special value indicating there is no pulse which means that
	 * {@link #hookPulse(Oscilloscope, int)} will never be called.
	 * 
	 * @see #getPulse()
	 */
	public static final int NO_PULSE = 0;

	private Image backgroundImage;

	private Oscilloscope scope;

	private int channel = -1;

	private boolean stop;

	private boolean isRunning;

	private Color activeForegroundColor = Display.getDefault().getSystemColor(SWT.COLOR_GREEN);
	private Color inactiveForegroundColor = Display.getDefault().getSystemColor(SWT.COLOR_RED);
	
	/**
	 * @param channel
	 * @see #getChannel()
	 */
	public OscilloscopeDispatcher(int channel) {
		this();
		this.channel = channel;
	}

	/**
	 * The default constructor.
	 */
	public OscilloscopeDispatcher() {
	}

	/**
	 * @param channel
	 * @param oscilloscope
	 * 
	 * @see OscilloscopeDispatcher#getChannel()
	 * @see OscilloscopeDispatcher#getOscilloscope()
	 */
	public OscilloscopeDispatcher(int channel, Oscilloscope oscilloscope) {
		this(channel);
		scope = oscilloscope;
	}

	/**
	 * @param oscilloscope
	 * 
	 * @see OscilloscopeDispatcher#getOscilloscope()
	 */
	public OscilloscopeDispatcher(Oscilloscope oscilloscope) {
		scope = oscilloscope;
	}

	/**
	 * This method will get the animation going. It will create a runnable that
	 * will be dispatched to the user interface thread. The runnable increments
	 * a counter that leads to the {@link #getPulse()} value and if this is
	 * reached then the counter is reset. The counter is passed to the hook
	 * methods so that they can prepare for the next pulse.
	 * <p/>
	 * After the hook methods are called, the runnable is placed in the user
	 * interface thread with a timer of {@link #getDelayLoop()} milliseconds.
	 * However, if the delay loop is set to 1, it will dispatch using
	 * {@link Display#asyncExec(Runnable)} for maximum speed.
	 * <p/>
	 * This method is not meant to be overridden, override {@link #init()}
	 * {@link #hookBeforeDraw(Oscilloscope, int)},
	 * {@link #hookAfterDraw(Oscilloscope, int)} and
	 * {@link #hookPulse(Oscilloscope, int)}.
	 * 
	 * 
	 */
	public synchronized void dispatch() {

		if (isRunning) {
			throw new RuntimeException("Scope is already running. Call stop() to stop the scope.");
		}

		isRunning = true;
		stop = false;

		init();

		Runnable runnable = new Runnable() {

			private int pulse;

			public void run() {
				if (getOscilloscope().isDisposed())
					return;

				hookBeforeDraw(getOscilloscope(), this.pulse);
				getOscilloscope().redraw();
				hookAfterDraw(getOscilloscope(), this.pulse);
				this.pulse++;

				if (this.pulse >= getPulse()) {
					if (getPulse() != OscilloscopeDispatcher.NO_PULSE) {
						hookPulse(getOscilloscope(), this.pulse);
					}
					this.pulse = 0;
				}

				if (!stop)
					if (getDelayLoop() > 1) {
						getOscilloscope().getDisplay().timerExec(getDelayLoop(), this);
					} else {
						getOscilloscope().getDisplay().asyncExec(this);
					}
				else {
					isRunning = false;
				}
			}
		};

		getOscilloscope().getDisplay().syncExec(runnable);

	}

	@Override
	protected void finalize() throws Throwable {
		if ((this.backgroundImage != null) && !this.backgroundImage.isDisposed()) {
			this.backgroundImage.dispose();
		}
	}

	/**
	 * Is used to get the color of the foreground when the thing that the scope
	 * is measuring is still alive. The aliveness of the thing that is being
	 * measured is returned by the {@link #isServiceActive()} method. The result
	 * of this method will be used in the
	 * {@link Oscilloscope#setForeground(Color)} method.
	 *
	 * @return the system color green.
	 *
	 * @see #getInactiveForegoundColor()
	 * @see #getActiveForegoundColor()
	 * @see #setActiveForegoundColor(Color)
	 * @see Oscilloscope#setForeground(Color)
	 */
	public Color getActiveForegoundColor() {
		return activeForegroundColor;
	}

	/**
	 * Is used to get the color of the foreground when the thing that the scope
	 * is measuring is still alive. The aliveness of the thing that is being
	 * measured is returned by the {@link #isServiceActive()} method. The result
	 * of this method will be used in the
	 * {@link Oscilloscope#setForeground(Color)} method.
	 *
	 * @param color the new color.
	 *
	 * @see #getInactiveForegoundColor()
	 * @see #getActiveForegoundColor()
	 * @see #getActiveForegoundColor()
	 */
	public void setActiveForegoundColor(final Color color) {
		activeForegroundColor = color;
	}

	/**
	 * Override this to return a soundfile that will be played by the dispatcher
	 * in the {@link #hookPulse(Oscilloscope, int)} method if the
	 * {@link #isSoundRequired()} method returns true.
	 * 
	 * @return <code>null</code>. Override to return a file that can be played
	 *         by your sound hardware.
	 */
	public File getActiveSoundfile() {
		return null;
	}

	/**
	 * Override this to return the background image for the scope.
	 * 
	 * @return the image stored in {@link #BACKGROUND_MONITOR}. Override to
	 *         supply your own Image.
	 */
	public Image getBackgroundImage() {
		if (this.backgroundImage == null) {
			this.backgroundImage = createGridImage();
		}
		return this.backgroundImage;
	}
	
	private Image createGridImage() {
		Display display = Display.getDefault();
		int width = getOscilloscope().getGridSquareSize();
		Image image = new Image(display, width, width);
		GC gc = new GC(image);

		gc.setAdvanced(true);
		gc.setBackground(getOscilloscope().getGridBackground());
		gc.fillRectangle(0, 0, width, width);

		gc.setForeground(getOscilloscope().getGridForeground());
		gc.setLineWidth(getOscilloscope().getGridLineWidth());
		gc.drawLine(width/2, 0, width/2, width);
		gc.drawLine(0, width/2, width, width/2);
		gc.dispose();
		return image;
	}


	/**
	 * Override this to set the offset of the scope line in percentages where
	 * 100 is the top of the widget and 0 is the bottom.
	 * 
	 * @return {@link Oscilloscope#BASE_CENTER} which positions in the center.
	 *         Override for other values.
	 */
	public int getBaseOffset() {
		return Oscilloscope.BASE_CENTER;
	}

	/**
	 * Will stop the animation.
	 */
	public void stop() {
		stop = true;
	}

	/**
	 * Override this to return the Clip player.
	 * <p/>
	 * Overriding this method is not expected.
	 * 
	 * @return the PlayClip object
	 */
	public SoundClip getSoundClip() {
		return this.clipper;
	}

	/**
	 * Override this to return a draw delay in milliseconds. The scope will
	 * progress {@link #getProgression()} steps.
	 * 
	 * @return 30 milliseconds. Override with a smaller value for more speed.
	 */
	public int getDelayLoop() {
		return 30;
	}

	/**
	 * Tests if the tail must fade.
	 * 
	 * @return this default implementation returns true
	 * @see Oscilloscope#setTailFade(int, int)
	 * @see #getTailSize()
	 */
	public boolean getFade() {
		return true;

	}

	/**
	 * Is used to get the color of the foreground when the thing that the scope
	 * is measuring is not active. The aliveness of the thing that is being
	 * measured is returned by the {@link #isServiceActive()} method. The result
	 * of this method will be used in the
	 * {@link Oscilloscope#setForeground(Color)} method.
	 *
	 * @return the system color red, override if you want to control the
	 *         inactive foreground color
	 *
	 * @see #setInactiveForegoundColor(Color)
	 * @see #getActiveForegoundColor()
	 * @see #setActiveForegoundColor(Color)
	 * @see Oscilloscope#setForeground(Color)
	 */
	public Color getInactiveForegoundColor() {
		return inactiveForegroundColor;
	}

	/**
	 * Is used to get the color of the foreground when the thing that the scope
	 * is measuring is still alive. The aliveness of the thing that is being
	 * measured is returned by the {@link #isServiceActive()} method. The result
	 * of this method will be used in the
	 * {@link Oscilloscope#setForeground(Color)} method.
	 *
	 * @param color the new color.
	 *
	 * @see #getInactiveForegoundColor()
	 * @see #getActiveForegoundColor()
	 * @see #setActiveForegoundColor(Color)
	 * @see Oscilloscope#setForeground(Color)
	 */
	public void setInactiveForegoundColor(final Color color) {
		this.inactiveForegroundColor = color;
	}

	/**
	 * @return this default implementation returns null
	 */
	public File getInactiveSoundfile() {
		return null;
	}

	/**
	 * @return this default implementation returns 1
	 */
	public int getLineWidth() {
		return 1;
	}

	/**
	 * This method returns the {@link Oscilloscope}.
	 * 
	 * @return the oscilloscope
	 */
	public Oscilloscope getOscilloscope() {
		return scope;
	}

	/**
	 * @return true if the dispatcher is running.
	 * @see #dispatch()
	 * @see #stop()
	 */
	public boolean isRunning() {
		return isRunning;
	}

	/**
	 * This method sets the {@link Oscilloscope}.
	 * 
	 * @param scope
	 */
	public void setOscilloscope(Oscilloscope scope) {
		this.scope = scope;
	}

	/**
	 * Override this to set the number of steps that is calculated before it is
	 * actually drawn on the display. This will make the graphic look more jumpy
	 * for slower/higher delay rates but you can win speed at faster/lower delay
	 * rates. There will be {@link #getProgression()} values consumed so make
	 * sure that the value stack contains enough entries.
	 * <p/>
	 * If the {@link #getDelayLoop()} is 10 and the {@link #getPulse()} is 1 and
	 * the {@link #getProgression()} is 5 then every 10 milliseconds the graph
	 * will have progressed 5 pixels. If you want to avoid gaps in your graph,
	 * you need to input 5 values every time you reach
	 * {@link #hookSetValues(int)}. If the {@link #getPulse()} is 3, you need to
	 * input 15 values for a gapless graph. Alternatively, you can implement a
	 * stack listener in the scope to let it call you in case it runs out of
	 * values.
	 * 
	 * @return 1. Override and increment for more speed. Must be higher then
	 *         zero.
	 */
	public int getProgression() {
		return 1;
	}

	/**
	 * Returns the heart beat of the scope.
	 * 
	 * @return this default implementation returns 1
	 * 
	 * @see #dispatch()
	 * @see #getDelayLoop()
	 * @see #getProgression()
	 */
	public int getPulse() {
		return 1;
	}

	/**
	 * If the scope value must be drawn on a steady position then this method
	 * can supply a value.
	 * 
	 * @return the default implementation returns 200
	 * @see Oscilloscope#setSteady(int, boolean, int)
	 */
	public int getSteadyPosition() {
		return 200;
	}

	/**
	 * @return the percentage of the tail that must fade.
	 * @see Oscilloscope#setTailFade(int, int)
	 */
	public int getTailFade() {
		return Oscilloscope.TAILFADE_DEFAULT;
	}

	/**
	 * @return the tail size
	 * @see Oscilloscope#setTailSize(int, int)
	 */
	public int getTailSize() {
		return Oscilloscope.TAILSIZE_DEFAULT;
	}

	/**
	 * Returns the channel that this scope is the dispatcher for. Please note
	 * that you do not really need one dispatcher per channel.
	 * 
	 * @return the channel for this dispatcher. Can be -1 if not set.
	 */
	public int getChannel() {
		return channel;
	}

	/**
	 * Is called just after the widget is redrawn every {@link #getDelayLoop()}
	 * milliseconds. The pulse counter will be set to zero when it reaches
	 * {@link #getPulse()}.
	 * 
	 * @param oscilloscope
	 * @param counter
	 */
	public void hookAfterDraw(Oscilloscope oscilloscope, int counter) {

	}

	/**
	 * Is called just before the widget is redrawn every {@link #getDelayLoop()}
	 * milliseconds. It will also call the {@link #hookChangeAttributes()}
	 * method if the number of times this method is called matches the
	 * {@link #getPulse()} value. The pulse counter will be set to zero when it
	 * reaches {@link #getPulse()}.
	 * <p/>
	 * If you override this method, don't forget to call
	 * {@link #hookChangeAttributes()} every now and then.
	 * 
	 * @param oscilloscope
	 * @param counter
	 */
	public void hookBeforeDraw(Oscilloscope oscilloscope, int counter) {
		if (counter == getPulse() - 1) {
			hookChangeAttributes();
		}
	}

	/**
	 * This method sets the values in the scope by calling the individual value
	 * methods in the dispatcher. Be aware that this method actually calls some
	 * candy so you might want to call super.
	 */
	public void hookChangeAttributes() {
		//
		getOscilloscope().setBackgroundImage(getBackgroundImage());

		for (int i = 0; i < getOscilloscope().getChannels(); i++) {

			getOscilloscope().setPercentage(i, isPercentage());
			getOscilloscope().setTailSize(i, isTailSizeMax() ? Oscilloscope.TAILSIZE_MAX : getTailSize());
			getOscilloscope().setSteady(i, isSteady(), getSteadyPosition());
			getOscilloscope().setFade(i, getFade());
			getOscilloscope().setTailFade(i, getTailFade());
			getOscilloscope().setConnect(i, mustConnect());
			getOscilloscope().setLineWidth(i, getLineWidth());
			getOscilloscope().setBaseOffset(i, getBaseOffset());
		}

	}

	/**
	 * This method is called every time the dispatcher reaches the getPulse()
	 * counter. This method plays the active or inactive sound if so required.
	 * If you do not want the sounds to play, either disable sounds by not
	 * overriding the {@link #isSoundRequired()} method or override this method.
	 * 
	 * @param oscilloscope
	 * @param pulse
	 */
	public void hookPulse(Oscilloscope oscilloscope, int pulse) {

		if (isServiceActive()) {
			getOscilloscope().setForeground(getActiveForegoundColor());

			hookSetValues(pulse);

			if (isSoundRequired()) {
				getSoundClip().playClip(getActiveSoundfile(), 0);
			}

		} else {

			if (isSoundRequired()) {
				getSoundClip().playClip(getInactiveSoundfile(), 0);
			}

			getOscilloscope().setForeground(getInactiveForegoundColor());
		}

	}

	/**
	 * This method will be called every {@link #getPulse()} times the scope is
	 * redrawn which will occur every {@link #getDelayLoop()} milliseconds (if
	 * your hardware is capable of doing so). The scope will progress one pixel
	 * every {@link #getDelayLoop()} milliseconds and will draw the next value
	 * from the queue of the scope. If the scope is out of values it will
	 * progress one pixel without a value (draw a pixel at his center).
	 * <p/>
	 * If the delay loop is 10 and the pulse is 20, you have an opportunity to
	 * set a value in the scope every 200 milliseconds. In this time the scope
	 * will have progressed 20 pixels. If you supply 10 values by calling the
	 * setValue(int) 10 times or if you call the setValues(int[]) with 10 ints
	 * then you will see 10 pixels of movement and a straight line of 10 pixels.
	 * <p/>
	 * If the setPulse method is not overridden or if you supply
	 * {@link #NO_PULSE} then this method will not be called unless you override
	 * the dispatch method (not recommended). To still set values in the scope
	 * you can set a stack listener in the widget that will be called when there
	 * are no more values in the stack. Alternatively you can set the return
	 * value of {@link #getPulse()} to 1 so you have the opportunity to provide
	 * a value every cycle.
	 * 
	 * @param pulse
	 * @see Oscilloscope#setValue(int)
	 * @see Oscilloscope#setValues(int[])
	 * @see Oscilloscope#addStackListener(OscilloscopeStackAdapter)
	 */
	public void hookSetValues(int pulse) {

	}

	/**
	 * Will be called only once.
	 */
	public void init() {
		hookChangeAttributes();
	}

	/**
	 * Indicates if the value that comes in from the scope is a percentage
	 * rather than an absolute value.
	 * 
	 * @return the default implementation returns true
	 * @see Oscilloscope#setPercentage(int, boolean)
	 */
	public boolean isPercentage() {
		return true;
	}

	/**
	 * A helper method to indicate if something that you are measuring is
	 * active. Based on the output of this method things can be done, like
	 * playing a flat-line sound, changing the foreground color and such.
	 * 
	 * @return the default implementation returns true
	 */
	public boolean isServiceActive() {
		return true;
	}

	/**
	 * The dispatcher is able to beep and pling and this method helps in
	 * indicating if this is required.
	 * 
	 * @return this default implementation returns false
	 * @see Oscilloscope#setSteady(int, boolean, int)
	 */
	public boolean isSoundRequired() {
		return false;
	}

	/**
	 * @return this default implementation returns false
	 * @see Oscilloscope#setSteady(int, boolean, int)
	 */
	public boolean isSteady() {
		return false;
	}

	/**
	 * @return this default implementation returns false.
	 * @see Oscilloscope#setTailSize(int, int)
	 */
	public boolean isTailSizeMax() {
		return false;
	}

	/**
	 * @return this default implementation returns false
	 * @see Oscilloscope#setConnect(int, boolean)
	 */
	public boolean mustConnect() {
		return false;
	}

	void updateBackgroundImage() {
		if (backgroundImage != null && !backgroundImage.isDisposed()) {
			backgroundImage.dispose();
		}
		backgroundImage = null;
	}
}
